import processing.xml.*;

/**
 * MFlickrViewer 
 * 
 * Flickr Viewer uses the Flickr REST services to provide a searcher and viewer
 * of public photos available in Flickr.
 *
 * To acces the Flickr services you must obtain an API Key you can visit the 
 * site http://www.flickr.com/services/api/keys/ and copy the key to the the 
 * file data/apikey.txt
 *
 * The application allow search photos given some text, press the Serch softkey
 * to provide the search text and navigate through the result using the LEFT 
 * and RIGHT key.
 *
 * @author Mary Jane Soft - Marlon J. Manrique
 * @version 0.1 (2007,06,07)
 *
 * $Id: MFlickrViewer.pde 249 2007-06-16 18:16:04Z marlonj $
 */

// Flickr values, service address, key and text to search
final String server = "api.flickr.com";
final String file = "/services/rest/";
String apikey;
String searchText = "";

// Network cliente to retrive search results
PClient pClient;
PRequest request;

// Network client to retrieve each photo
PClient photoClient;
PRequest photoRequest;

// XML parser to grab search results 
XMLParser parser;

// Corrdinates of the set of photos
int x;
int y;

// Photo information
Photo[] photos; // Array of photos with all the info
int totalPhotos;  // Total number of photos 
int photoCounter; // Loading photo counter
int pagePhotos; // Number of photos per page
int maxPhotos = 5; // Max of photos per page
int currentPage = 1; // Current result page
int photoSize = 75; // Size of each ptoho
int currentPhoto; // Current selected photo
int currentX; // Position x of the current photo

// Status information
boolean loading; // If the application are loading photos 
int dir = RIGHT; // Direction of the navigation

// Text configuration and preload images 
PFont font; // Font information
String message = "Search and Browse"; // Status message
PImage loadingImage; // Image to show when the photo is loading

// Data structure to hold Photo Information 
class Photo
{
  String serverName; // The server where the photo is
  String file; // The file name of the photo
  PImage pImage; // The load image
}

/**
 * Start the application.
 *
 * Create objects, load font and load apikey
 */
void setup()
{
  // Create result parser
  parser = new XMLParser(this);

  // Load a system font
  font = loadFont(FACE_SYSTEM,STYLE_PLAIN,SIZE_MEDIUM);
  textFont(font);

  // Preload the loading image
  loadingImage = loadImage("flickr.png");

  // Set text properties and add a softkey
  textAlign(CENTER);
  softkey("Search");

  // Load the apikey
  loadApikey();
}

/**
 * Draw the info and photos 
 */
void draw()
{
  // Clear the scene and move to the center of the screen
  background(0);
  translate(width/2,height/2);  

  // Viewer position
  int tmpX = 0;
  int tmpY = - photoSize/2;

  // If the photos are available
  if(photos != null)
  {
    // Draw each photo
    for(int i=0; i<photos.length; i++)
    {
      // If the photo is available 
      if(photos[i] != null)
      {                
        // If the photo is the current one
        // Draw a green rect over the photo
        // and set current position 
        if(i == currentPhoto)
        {
          noFill();
          stroke(0,255,0);
          rect(x+tmpX-photoSize/2-2,y+tmpY-2,photoSize+4,photoSize+4);
          currentX = tmpX;
        }

        // Get the image of the photo        
        PImage pImage = photos[i].pImage;

        // If the image is available show it
        if(pImage != null)
          image(pImage,x+tmpX-photoSize/2,y+tmpY);
        // Draw the loading image
        else
          image(loadingImage,x+tmpX-photoSize/2,y+tmpY);          

        // Update the position of the next photo 
        tmpX += photoSize + 8;     
      }
    }
  }

  // Draw application information
  text("Flickr Viewer",0,y-photoSize/2-font.height);
  text(searchText,0,y+photoSize/2+font.height+2);
  text(message,0,y+photoSize/2+2*font.height+2);   

  // If the x value is not equal to the current
  // move the position of the set each time to center the
  // current photo
  if(x > -currentX) x -= 5;
  if(x < -currentX) x += 5;
}

/**
 * Navigate through the photo set
 */
void keyPressed()
{
  switch(keyCode)
  {
    // If go the left
  case LEFT : 
    currentPhoto--;

    // If current photo is out of the set, load the below page
    if(currentPhoto < 0)
    {
      currentPhoto = 0;

      // Check if the page is not the first 
      // and the application is not loading 
      if(currentPage > 1 && !loading)
      {
        // Reset values to show the last photo of the below page      
        dir = LEFT;      
        currentPage--;
        currentPhoto = maxPhotos - 1;
        x = -332;
        currentX = 332;
        searchPhotos();
      }
    }
    break;

    // If go the right
  case RIGHT :
    currentPhoto++;

    // If current photo is out of the set, load the next page
    if(currentPhoto >= maxPhotos)
    {
      currentPhoto = maxPhotos - 1;

      // Check if the page is not the last
      // and the application is not loading       
      if(currentPage < totalPhotos/maxPhotos && !loading)
      {
        // Reset values to show the first photo of the next page
        dir = RIGHT;		
        currentPage++;
        currentPhoto = 0;        
        x = 0;
        currentX = 0;
        searchPhotos();
      }
    }
    break;    
  }
}

/**
 * Check the sofkey
 */
void softkeyPressed(String label)
{
  // If the user press the Search softkey 
  // and the application is loading photos
  if(label.equals("Search") && !loading)
  { 
    // Get the search text from the user
    String tmpText = textInput("Search",searchText,100);

    // If no cancel
    if(searchText != null)
    {
      // Reset viewer values      
      currentPage = 1;
      currentPhoto = 0;
      currentX = 0;
      x = 0;      
      searchText = tmpText;

      // Search photos 
      searchPhotos();
    }
  }
}

/**
 * Listen the events of the search and the photo loading
 */
void libraryEvent(Object library, int event, Object data)
{
  // If the search request
  if(library == request)
  {
    //  Get the search result
    if(event == PRequest.EVENT_CONNECTED)
      parser.start(request);      
    // If any error
    else if(event == PRequest.EVENT_ERROR)
      message = "Error : " + data;
  }
  // If the photo request 
  else if(library == photoRequest)
  {
    // Get the photo data
    if(event == PRequest.EVENT_CONNECTED)
      photoRequest.readBytes();
    // Set the photo in the current set
    else if(event == PRequest.EVENT_DONE)
      addPhoto(loadImage((byte[])data));
    // If any error
    else if(event == PRequest.EVENT_ERROR)
      message = "Error : " + data;      
  }  
  // If the result of the search
  else if(library == parser)
  {
    if(event == XMLParser.EVENT_TAG_START)
    { 
      // Check response status
      if(data.equals("rsp"))
      {
        // Get status 
        String stat = parser.attribute("stat");
        
        if(!stat.equals("ok"))
          message = "Error";
      }
      // Set error message
      else if(data.equals("err"))
          message = "Error: "  + parser.attribute("msg");
      // If photos information, get total information     
      else if(data.equals("photos"))        
      {
        // Get set information
        pagePhotos = int(parser.attribute("perpage"));
        totalPhotos = int(parser.attribute("total"));

        // If no photos found, show a message
        if(totalPhotos == 0) {
          message = "Photos Not Found";
          loading = false;
        }

        // If no set complete, get the total 
        if(totalPhotos < maxPhotos)
          photos = new Photo[totalPhotos];
        else
          photos = new Photo[pagePhotos];
      }
      // If photo information add new photo
      else if(data.equals("photo"))
      {
        // Get information
        String farm = parser.attribute("farm");
        String server = parser.attribute("server");
        String id = parser.attribute("id");
        String secret = parser.attribute("secret");
        
        // Create paths
        String photoServer = "farm" + farm + ".static.flickr.com";
        String photoFile = "/" + server + "/" + id + "_" + secret + "_s.jpg";
        
        // Add the photo to the current set
        addPhoto(photoServer,photoFile);
      }
    }
  }
}

/**
 * Load the photos at the current page with the search text.
 */
void searchPhotos()
{
  // Reset photo information
  photoCounter = 0;
  photos = null;
  loading = true;

  // Show the loading message
  message = "Loading ...";

  // Parameters and values of the search
  String[] params = { 
    "api_key", "method", "text", "per_page", "page"   };
  String[] values = { 
    apikey, "flickr.photos.search", searchText, 
    str(maxPhotos), str(currentPage)   };

  // Request the information to the server
  pClient = new PClient(this,server);
  request = pClient.POST(file,params,values);  
}

/**
 * Load the current photo
 */
void loadPhoto()
{
  // Get the index of the photo
  int index = photoCounter;

  // If the direction is left, load from right to left
  if(dir == LEFT)
    index = maxPhotos - photoCounter -1;

  // Request the photo to the server
  photoClient = new PClient(this,photos[index].serverName);
  photoRequest = photoClient.GET(photos[index].file);
}

/**
 * Load the apikey from a text file at the data directory, if no apikey is 
 * found, show a message.
 *
 * You can request your apikey at http://www.flickr.com/services/api/keys
 * copy the key value to the file data/apikey.txt
 */
void loadApikey()
{
  // Load all lines of the file
  String[] lines = loadStrings("apikey.txt");

  // The file have only a line with the apikey
  if(lines.length != 1)
    message = "Error : Unable to load API Key";
  else
    apikey = lines[0];
}

/**
 * Add the photo to the current set
 */
void addPhoto(PImage pImage)
{
  // Set current photo index
  int index = photoCounter;

  // If the direction is left, load from right to left 
  if(dir == LEFT)
    index = maxPhotos-photoCounter-1;

  // Set photo image
  photos[index].pImage = pImage;

  // Next photo
  photoCounter++;

  // If are more photos to load, load next
  // else application finnish the photo loading
  if(photoCounter != length(photos))
    loadPhoto();  
  else
    loading = false;      
}

/**
 * Add the photo information 
 */
void addPhoto(String photoServer, String photoFile)
{
  // Create the photo and set information
  Photo p = new Photo();
  p.serverName = photoServer;
  p.file = photoFile;

  // Add the photo to the set
  photos[photoCounter++] = p;

  // If last photo, start loading
  if(photoCounter == length(photos))
  {
    photoCounter = 0;
    loadPhoto();
  }
  // Show page information
  else
    message = "Page " + currentPage + " of " + totalPhotos/maxPhotos;
}
